<?php
/**
 * Description: 商品复制服务类
 * Author: hezhizheng
 * Date: 2018/9/7
 * Time: 19:38
 * Created by Created by Panxsoft.
 */

namespace App\Services\Product;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use App\Models\Admin\CosmeticMaterial;
use App\Models\Customer\Asset;
use App\Models\Customer\Customer;
use App\Models\Panx\Brand;
use App\Models\Panx\BrandSeries;
use App\Models\Panx\Category;
use App\Models\Panx\Product;
use App\Models\Panx\ProductImage;
use App\Services\Utils\ApiChecker;
use Illuminate\Database\Eloquent\Collection;

class CopyProductService
{
    use ApiChecker;

    /**
     * 将指定产品复制给指定 customer
     *
     * @param Product $old_prod
     * @param Customer $customer
     *
     * @return Product
     * @throws \Exception
     */
    public function toCustomer(Product $old_prod, Customer $customer)
    {
        $changes = $this->copyChangeProperties($old_prod, $customer);
        $sames = $this->copyProductProperties($old_prod);
        $belongs = $this->copyProductBelongs($old_prod, $customer);

        /** @var Product $new_prod */
        $new_prod = (new Product)->create($changes + $sames + $belongs);

        $this->copyProductImages($old_prod, $new_prod);
        $this->copyProductTags($old_prod, $new_prod);
        $this->copyProductBodyPositions($old_prod, $new_prod);

        $old_prod->incrementRefCount();

        return $new_prod;
    }

    /**
     * 提取产品自身属性（无关联）
     *
     * @param Product $old_prod
     *
     * @return array
     */
    private function copyProductProperties(Product $old_prod): array
    {
        return [
            // props
            'name'         => $old_prod->name,
            'title'        => $old_prod->title,
            'description'  => $old_prod->description,
            'market_price' => $old_prod->market_price,
            'sell_price'   => $old_prod->sell_price,
            'stock'        => $old_prod->stock,
            'product_url'  => $old_prod->product_url,
            'sex'          => $old_prod->sex,
            'is_sale'      => $old_prod->is_sale,
            'is_deleted'   => $old_prod->is_deleted,
        ];
    }

    /**
     * BelongsTo
     *
     * @param Product $old_prod
     * @param Customer $customer
     *
     * @return array
     * @throws \Exception
     */
    private function copyProductBelongs(Product $old_prod, Customer $customer): array
    {
        $customer_id = $customer->getKey();

        $brand_id = $this->copyOrSpecifiedBrandId($old_prod, $customer_id);
        $brand_series_id = $this->copyOrSpecifiedBrandSeriesId($old_prod, $brand_id, $customer_id);

        $asset_id = $old_prod->asset
            ? $this->copyAsset($old_prod->asset, $customer_id)->id
            : 0;
        $cos_mat_id = $old_prod->cosmeticMaterial
            ? $this->copyCosmeticMaterial($old_prod->cosmeticMaterial, $customer_id)->id
            : 0;

        return [
            // belongs to relations
            'customer_id'          => $customer_id,
            'store_id'             => Product::STORE_ID_CUSTOMER,

            // panx default relation
            'category_id'          => $old_prod->category_id,
            'product_url_type_id'  => $old_prod->product_url_type_id,

            // customer self create
            'asset_id'             => $asset_id,
            'brand_id'             => $brand_id,
            'brand_series_id'      => $brand_series_id,
            'cosmetic_material_id' => $cos_mat_id,

            // new belongs id
            'src_product_id'       => $old_prod->id,
        ];
    }

    /**
     * @param Brand $brand
     * @param int $customer_id
     *
     * @return Brand
     */
    private function copyBrand(Brand $brand, int $customer_id): Brand
    {
        /** @var Brand $new_brand */
        $new_brand = (new Brand)->firstOrCreate([
            'customer_id' => $customer_id,
            'name'        => $brand->name,
        ], [
            'url'         => $brand->url,
            'logo'        => $brand->logo,
            'locked'      => $brand->locked,
        ]);

        return $new_brand;
    }

    /**
     * @param BrandSeries $old_series
     * @param int $new_brand_id
     * @param int $customer_id
     *
     * @return BrandSeries
     */
    private function copyBrandSeries(BrandSeries $old_series,
                                     int $new_brand_id,
                                     int $customer_id): BrandSeries
    {
        /** @var BrandSeries $new_brand_series */
        $new_brand_series = (new BrandSeries)->firstOrCreate([
            'customer_id' => $customer_id,
            'brand_id'    => $new_brand_id,
            'name'        => $old_series->name,
        ], [
            'image'       => $old_series->image,
            'sort'        => $old_series->sort,
        ]);

        return $new_brand_series;
    }

    /**
     * 只复制素材，整妆不复制
     *
     * @param Asset $asset
     * @param int $customer_id
     *
     * @return Asset
     */
    public function copyAsset(Asset $asset, int $customer_id): Asset
    {
        /** @var Asset $new_asset */
        $new_asset = (new Asset)->firstOrCreate([
            'customer_id' => $customer_id,
            'category_id' => $asset->category_id,
            'code'        => $asset->code,
        ], [
            'store_id'    => 0,
            'model_xml'   => $asset->model_xml,
            'version'     => $asset->version,
            'src_asset_id'=> $asset->id,
        ]);

        return $new_asset;
    }

    /**
     * @param CosmeticMaterial $old_cos_mat
     * @param int $customer_id
     *
     * @return CosmeticMaterial
     */
    private function copyCosmeticMaterial(CosmeticMaterial $old_cos_mat,
                                          int $customer_id): CosmeticMaterial
    {
        /** @var CosmeticMaterial $new_cos_mat */
        $new_cos_mat = (new CosmeticMaterial)->firstOrCreate([
            'customer_id' => $customer_id,
            'name'        => $old_cos_mat->name,
        ], [
            'sort'        => $old_cos_mat->sort,
        ]);

        return $new_cos_mat;
    }

    /**
     * HasMany
     *
     * @param Product $old
     * @param Product $new
     *
     * @return bool
     */
    private function copyProductImages(Product $old, Product $new): bool
    {
        $now = now()->toDateTimeString();

        $new_imgs_data = $old->productImages
            ->map(function (ProductImage $img) use ($new, $now) {
                return [
                    'product_id' => $new->id,
                    'type'       => $img->type,
                    'key'        => $img->key,
                    'created_at' => $now,
                    'updated_at' => $now,
                ];
            })
            ->all();

        return (new ProductImage)->insert($new_imgs_data);
    }

    /**
     * @param Product $old
     * @param Product $new
     */
    private function copyProductTags(Product $old, Product $new): void
    {
        $old_tag_ids = $old->productTags->modelKeys();

        $new->productTags()->attach($old_tag_ids);
    }

    /**
     * @param Product $old
     * @param Product $new
     */
    private function copyProductBodyPositions(Product $old, Product $new): void
    {
        $old_body_pos_ids = $old->bodyPositions->modelKeys();

        $new->bodyPositions()->attach($old_body_pos_ids);
    }

    /**
     * 复制需要动态变化的属性
     *
     * @param Product $old_prod
     * @param Customer $customer
     *
     * @return array
     */
    private function copyChangeProperties(Product $old_prod, Customer $customer): array
    {
        $copied_counts = $customer->products()->where('src_product_id', $old_prod->id)->count();
        $new_spu_number = $copied_counts + 1;

        return [
            'spu_code' => "{$old_prod->spu_code}($new_spu_number)",
        ];
    }

    /**
     * @param Collection $products
     * @param Customer $customer
     *
     * @return Collection - new Products
     * @throws \Exception
     */
    public function batchToCustomer(Collection $products, Customer $customer): Collection
    {
        $this->checkCustomerHasProductsCategories($products, $customer);

        return $products->map(function (Product $product) use ($customer) {
            return $this->toCustomer($product, $customer);
        });
    }

    /**
     * @param array $need_category_ids
     * @param Customer $customer
     * @throws \Exception
     */
    public function checkCustomerHasCategories(array $need_category_ids, Customer $customer): void
    {
        $category = new Category;

        $has_ids = $customer
            ->categories
            ->pluck('id')
            ->all();

        $lack_ids = array_values(array_diff($need_category_ids, $has_ids));

        if (count($lack_ids)) {
            $lack_names = $category
                ->whereKey($lack_ids)
                ->pluck('name')
                ->implode('，');

            throw new ApiException(ApiErrCode::OPERATE_FAILED, '客户缺少以下分类：' . $lack_names);
        }
    }

    /**
     * @param Collection $products
     * @param Customer $customer
     * @throws \Exception
     */
    private function checkCustomerHasProductsCategories(Collection $products, Customer $customer): void
    {
        $category_ids = $products->pluck('category_id')->unique()->all();

        $this->checkCustomerHasCategories($category_ids, $customer);
    }

    /**
     * @param Product $old_prod
     * @param int $customer_id
     *
     * @return int|string
     */
    private function copyOrSpecifiedBrandId(Product $old_prod, int $customer_id): int
    {
        if ($old_prod->new_brand_id) {
            return $old_prod->new_brand_id;
        }

        return $old_prod->brand
            ? $this->copyBrand($old_prod->brand, $customer_id)->id
            : 0;
    }

    /**
     * @param Product $old_prod
     * @param int $new_brand_id
     * @param int $customer_id
     *
     * @return int
     */
    private function copyOrSpecifiedBrandSeriesId(Product $old_prod, int $new_brand_id, int $customer_id): int
    {
        if (Category::isSkinCare($old_prod->category_id)) {
            return 0;
        }

        if ($old_prod->new_brand_series_id) {
            return $old_prod->new_brand_series_id;
        }

        return $old_prod->brandSeries
            ? $this->copyBrandSeries($old_prod->brandSeries, $new_brand_id, $customer_id)->id
            : 0;
    }
}